#pragma once

#include <Windows.h>
#include <sstream>

#include <string>
#include <filesystem>
#include <locale>
#include <codecvt>
#include <map>
/*
* Some functions for easy use in other places
*/
namespace BackupFS
{
#define W2S(wstr) WstringToString(wstr)
#define S2W(str) StringToWtring(str)

	inline std::string WstringToString(const std::wstring& wstr)
	{
		const std::string str = std::filesystem::path(wstr).string();
		return str;
		
	}
	inline std::wstring StringToWtring(const std::string& str)
	{
		const std::wstring wstr = std::filesystem::path(str).wstring();
		return wstr;
	}
	inline LONGLONG ParseSizeWithUnit(const std::string& sizeStr) 
	{
		std::istringstream iss(sizeStr);
		LONGLONG size;
		char unit;
		if (iss >> size >> unit)
		{
			switch (std::tolower(unit)) {
			case 'g':
			case 'gb':
				return size * 1024 * 1024 * 1024;
			case 'm':
			case 'mb':
				return size * 1024 * 1024;
			case 'k':
			case 'kb':
				return size * 1024;
			default: break;
			}
		}
		return 0;
	}
	inline void RemoveTrailingSlash(std::wstring &str) 
	{
		while (str.back() == '\\' || str.back() == '/') 
		{
			str.erase(str.size() - 1);
		}
	}
	class BackupFSUtils
	{
	public:
		static inline bool Seek(HANDLE file, LONGLONG pos)
		{
			LARGE_INTEGER offset = { 0 };
			offset.QuadPart = pos;
			if (SetFilePointerEx(file, offset, NULL, FILE_BEGIN))
				return true;
			return false;
		}
		static inline bool ReadFileData(HANDLE file, LPVOID buffer,LONGLONG pos, LONGLONG length,LONGLONG &read_size)
		{
			if (!Seek(file, pos))
			{
				return false;
			}
			DWORD size = 0;
			if (!ReadFile(file, buffer, (DWORD)length, &size, NULL))
			{
				return false;
			}
			read_size = size;
			return true;
		}
		static inline bool WriteFileData(HANDLE file, LPVOID buffer, LONGLONG pos, LONGLONG length, LONGLONG& written_size)
		{
			if (!Seek(file, pos))
			{
				return false;
			}
			DWORD size = 0;
			if (!WriteFile(file, buffer, (DWORD)length, &size, NULL))
			{
				return false;
			}
			written_size = size;
			return true;
		}
		static inline std::wstring GetFileName(const std::wstring& path)
		{
			std::size_t x = path.find_last_of('\\');
			return path.substr(x + 1);
		}

		static inline std::wstring GetParentPath(const std::wstring& path)
		{
			std::size_t x = path.find_last_of('\\');
			if (x == 0)
				return L"\\";
			return path.substr(0, x);
		}

		static inline LONGLONG FileTimeToLlong(const FILETIME& f)
		{
			return DDwLowHighToLlong(f.dwLowDateTime, f.dwHighDateTime);
		}

		static inline void LlongToFileTime(LONGLONG v, FILETIME& filetime)
		{
			LlongToDwLowHigh(v, filetime.dwLowDateTime, filetime.dwHighDateTime);
		}
		static void TimetToFileTime(time_t t, LPFILETIME pft)
		{
			ULARGE_INTEGER time_value;
			time_value.QuadPart = (t * 10000000LL) + 116444736000000000LL;
			pft->dwLowDateTime = time_value.LowPart;
			pft->dwHighDateTime = time_value.HighPart;
		}
		static LONGLONG FileTimeToTime(LPFILETIME pft)
		{
			ULARGE_INTEGER ui;
			ui.LowPart = pft->dwLowDateTime;
			ui.HighPart = pft->dwHighDateTime;
			ui.QuadPart -= 116444736000000000ULL;
			ui.QuadPart /= 10;
			return static_cast<time_t>(ui.QuadPart / 1000000);
		}
		static inline LONGLONG DDwLowHighToLlong(const DWORD& low,
			const DWORD& high)
		{
			return static_cast<LONGLONG>(high) << 32 | low;
		}

		static inline void LlongToDwLowHigh(const LONGLONG& v, DWORD& low,
			DWORD& hight)
		{
			hight = v >> 32;
			low = static_cast<DWORD>(v);
		}

		static const std::wstring DataStreamNameStr;
		// Remove the stream type from the filename
		// Stream type are not supported so we ignore / remove them.
		static inline void RemoveStreamType(std::wstring& filename) 
		{
			// Remove $DATA stream if exist as it is the default / main stream.
			auto data_stream_pos = filename.rfind(DataStreamNameStr);
			if (data_stream_pos == (filename.length() - DataStreamNameStr.length()))
			{
				filename = filename.substr(0, data_stream_pos);
			}
			// TODO: Remove $INDEX_ALLOCATION & $BITMAP
		}

		// Return a pair containing for example for \foo:bar
		// first: filename: foo
		// second: alternated stream name: bar
		// If the filename do not contain an alternated stream, second is empty.
		static inline std::pair<std::wstring, std::wstring> GetStreamNames(
			const std::wstring& filename) {
			// real_fileName - foo or foo:bar
			const auto real_fileName = BackupFSUtils::GetFileName(filename);
			auto stream_pos = real_fileName.find(L":");
			// foo does not have alternated stream, return an empty alternated stream.
			if (stream_pos == std::string::npos)
				return std::pair<std::wstring, std::wstring>(real_fileName,
					std::wstring());

			// foo:bar has an alternated stream
			// return first the file name and second the file stream name
			// first: foo - second: bar
			const auto main_stream = real_fileName.substr(0, stream_pos);
			++stream_pos;
			const auto alternate_stream =
				real_fileName.substr(stream_pos, real_fileName.length() - stream_pos);
			return std::pair<std::wstring, std::wstring>(main_stream, alternate_stream);
		}

		// Return the filename without any stream informations.
		// <filename>:<stream name>:<stream type>
		static inline std::wstring GetFileNameStreamLess(
			const std::wstring& filename,
			const std::pair<std::wstring, std::wstring>& stream_names)
		{
			auto file_name = BackupFSUtils::GetParentPath(filename);
			if (file_name != L"\\") {
				// return \ when filename is at root.
				file_name += L"\\";
			}
			file_name += stream_names.first;
			return file_name;
		}
		static inline std::string ReplaceAll(const std::string& input, std::map<char, std::string>chars)
		{
			std::string result;
			for (char ch : input)
			{
				if (chars.find(ch) != chars.end())
				{
					result += chars[ch];
				}
				else 
				{
					result += ch;
				}
			}
			return result;
		}
	};
}  // namespace BackupFS
